package huffmanTree;


import com.cn.stepLock.lambda.LambdaUtil;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.util.*;
import java.util.stream.Collectors;

import static huffmanTree.ByteUtil.combineBytes;
import static huffmanTree.ByteUtil.getByteByString;
import static huffmanTree.Utils.throwFunctionSupportException;

/**
 * @author Aaron
 * @desc
 * @createTime 2021/12/10 20:57
 * @since
 */
public class AlterableHuffmanTree<T> extends AbstractHuffmanTree<T> {


    public AlterableHuffmanTree() {
        super();
    }

    public AlterableHuffmanTree(List<T> valueList) {
        super(valueList);
    }

    public AlterableHuffmanTree(T value) {
        super(value);
    }

    @Override
    public void addValue(T value) {
        throwFunctionSupportException();
        this.valueList.add(value);
        if (valueFrequency.get(value)==null){
            valueFrequency.put(value,1L);
            this.huffmanTrees.offer(new HuffmanLeaf<>(1L,value));
        }else {
            valueFrequency.put(value,valueFrequency.get(value)+1L);
        }
        rebuildTree();
        Map<String, String> newAndAncientValueMapping = rebuildCompressMappingResult();
        rebuildCompressResult(newAndAncientValueMapping);
        postHandleBuildTree();
    }

    private void rebuildTree() {
        List<HuffmanNode<T>> allHuffmanTreeList = getAllHuffmanTreeList();
        doRebuildTree(allHuffmanTreeList);
    }

    private void doRebuildTree(List<HuffmanNode<T>> allHuffmanTreeList) {
        int deep = getDeep();
        PriorityQueue<HuffmanNode<T>> huffmanTreePriorityQueue = new PriorityQueue<>();
        List<HuffmanNode<T>> huffmanNodeList = filterHuffmanNodes(allHuffmanTreeList);
        huffmanNodeList.forEach(huffmanTreePriorityQueue::offer);
        buildAllLeaf();
        buildAllNode();
//        while (this.huffmanTrees.size()>=2){
//            HuffmanNode<T> huffmanNode;
//            if (huffmanTreePriorityQueue.size()>0){
//                huffmanNode = huffmanTreePriorityQueue.poll();
//                huffmanNode.left = huffmanTrees.poll();
//                huffmanNode.right = huffmanTrees.poll();
//                huffmanNode.frequency = huffmanNode.left.frequency+huffmanNode.right.frequency;
//            }else {
//                huffmanNode =new HuffmanNode(huffmanTrees.poll().setDeep(deep),huffmanTrees.poll().setDeep(deep));
//            }
//            this.huffmanTrees.offer(huffmanNode);
//            deep = deep-1;
//        }
//        this.root = this.huffmanTrees.poll().setDeep(deep);
    }

    private Map<String, String> rebuildCompressMappingResult() {
        //compressMappingResult = HashBiMap.create();
        BiMap<T,String> freshCompressMappingResult = HashBiMap.create();
        buildCompressMappingResult(root,new StringBuffer(), freshCompressMappingResult);
        Map<String, String> originalAndFreshValueMapping  = new HashMap<>();
        compressMappingResult.forEach((x,y)->{
            originalAndFreshValueMapping.put(y, freshCompressMappingResult.get(x));
        });

        compressMappingResult = freshCompressMappingResult;
        return originalAndFreshValueMapping;
        //rebuildCompressResult(newCompressMappingResult);
    }

    private void rebuildCompressResult(Map<String, String> newAndAncientValueMapping) {
        lastBitIndex = calculateLastBitIndex();
        this.compressResult = doRebuildCompressResult(new RebuildCompressResultVO(newAndAncientValueMapping,this.compressResult,new byte[calculateCompressResultLength()],lastBitIndex));
        this.compressByteBuf = Unpooled.copiedBuffer(this.compressResult);
    }

    private class RebuildCompressResultVO {
         Map<String, String> freshAndOriginalValueMapping;
         byte[] freshCompressResult;
         int lastBitIndex;
         int lastFillIndex;
         byte[] originalCompressResult;
         private int originalLastBitIndex;

        public RebuildCompressResultVO(Map<String, String> freshAndOriginalValueMapping, byte[] originalCompressResult,byte[] freshCompressResult,int originalLastBitIndex) {
            this.freshAndOriginalValueMapping = freshAndOriginalValueMapping;
            this.originalCompressResult = originalCompressResult;
            this.freshCompressResult = freshCompressResult;
            this.originalLastBitIndex = originalLastBitIndex;
        }
    }
    private void changeOriginalCompressResult(RebuildCompressResultVO rebuildCompressResultVO){
        StringBuilder originalBuilder = new StringBuilder();
        StringBuilder freshBuilder = new StringBuilder();
        final char trueString = 49;
        final char falseString = 48;
        byte[] freshCompressResult = rebuildCompressResultVO.freshCompressResult;
        rebuildCompressResultVO.lastFillIndex = 0;
        loop:for (int i = 0; i < rebuildCompressResultVO.originalCompressResult.length; i++) {
            byte b = rebuildCompressResultVO.originalCompressResult[i];
            int byteLength = 1<<3;
            if (i==rebuildCompressResultVO.originalCompressResult.length-1){
                byteLength = rebuildCompressResultVO.originalLastBitIndex+1;
            }
            for (int j = 0; j < byteLength; j++) {
                if (ByteUtil.isTrueAtBitIndex(b,j)){
                    originalBuilder.append(trueString);
                }else {
                    originalBuilder.append(falseString);
                }
                if (rebuildCompressResultVO.freshAndOriginalValueMapping.get(originalBuilder.toString())==null){
                    continue;
                }
                freshBuilder.append(rebuildCompressResultVO.freshAndOriginalValueMapping.get(originalBuilder.toString()));
                originalBuilder=new StringBuilder();
                if (i==rebuildCompressResultVO.originalCompressResult.length-1 && j==byteLength-1){
                    byte[] byteByString = getByteByString(freshBuilder.toString());
                    for (byte b1 : byteByString) {
                        freshCompressResult[rebuildCompressResultVO.lastFillIndex] =b1;
                        rebuildCompressResultVO.lastFillIndex=rebuildCompressResultVO.lastFillIndex+1;
                    }
                    rebuildCompressResultVO.lastBitIndex = calculateLastBitIndex(freshBuilder.length());
                    break loop;
                }
                if ((freshBuilder.length()>>3)==0){
                    continue;
                }
                Integer integer = Integer.parseInt(freshBuilder.substring(0, 8), 2);
                freshBuilder = freshBuilder.delete(0,8);
                freshCompressResult[rebuildCompressResultVO.lastFillIndex]=integer.byteValue();
                rebuildCompressResultVO.lastFillIndex = rebuildCompressResultVO.lastFillIndex+1;
            }
        }
        rebuildCompressResultVO.freshCompressResult=freshCompressResult;
    }


    private byte[] doRebuildCompressResult(RebuildCompressResultVO rebuildCompressResultVO) {
        changeOriginalCompressResult(rebuildCompressResultVO);
        byte[] newAddComressResult = doBuildCompressResult();
        combineCompressResult(rebuildCompressResultVO, newAddComressResult);
        return rebuildCompressResultVO.freshCompressResult;
    }

    private void combineCompressResult(RebuildCompressResultVO rebuildCompressResultVO, byte[] newAddComressResult) {
        rebuildCompressResultVO.lastFillIndex=rebuildCompressResultVO.lastFillIndex-1;
        /**
         * 如果旧数据占满所有预计的数组则报错
         */
        if (rebuildCompressResultVO.lastFillIndex==rebuildCompressResultVO.freshCompressResult.length-1 && rebuildCompressResultVO.lastBitIndex==7){
            throw new ArrayIndexOutOfBoundsException("the data has be full,may be some error in data");
        }
        if (rebuildCompressResultVO.lastBitIndex==7){
            for (int i = 0; i < newAddComressResult.length ; i++) {
                try {
                    rebuildCompressResultVO.freshCompressResult[rebuildCompressResultVO.lastFillIndex+1+i]=newAddComressResult[i];
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        if (rebuildCompressResultVO.lastBitIndex<7){
            ByteBuf byteBuf = Unpooled.copiedBuffer(newAddComressResult);
            byte left = byteBuf.readByte();
            rebuildCompressResultVO.freshCompressResult[rebuildCompressResultVO.lastFillIndex] = (byte)((rebuildCompressResultVO.freshCompressResult[rebuildCompressResultVO.lastFillIndex] & 0xff)+(((left & 0xFF) >>(rebuildCompressResultVO.lastBitIndex+1))&0xff));
            while (byteBuf.isReadable()){
                rebuildCompressResultVO.lastFillIndex = rebuildCompressResultVO.lastFillIndex+1;
                byte right = byteBuf.readByte();
                rebuildCompressResultVO.freshCompressResult[rebuildCompressResultVO.lastFillIndex]=combineBytes(left,right,7-rebuildCompressResultVO.lastBitIndex);
                left = right;
            }
            rebuildCompressResultVO.lastFillIndex = rebuildCompressResultVO.lastFillIndex+1;
            if (rebuildCompressResultVO.lastFillIndex<=rebuildCompressResultVO.freshCompressResult.length-1) {
                rebuildCompressResultVO.freshCompressResult[rebuildCompressResultVO.lastFillIndex] = (byte) (left << (7 - rebuildCompressResultVO.lastBitIndex));
            }
        }
    }


    @Override
    public void deleteValue(T node) {
        throwFunctionSupportException();
        this.valueList.remove(node);
        valueFrequency.remove(node);
        List<HuffmanNode<T>> allHuffmanTreeList = getAllHuffmanTreeList();
        allHuffmanTreeList.remove(getHuffmanLeaf(node));
        doRebuildTree(allHuffmanTreeList);
        rebuildCompressMappingResult();
    }

    @Override
    public void addValueList(List<T> valueList) {
        throwFunctionSupportException();
        this.valueList.addAll(valueList);
        List<T> notExitValue = LambdaUtil.filterList(valueList, x -> valueFrequency.get(x) == null);
        if (!notExitValue.isEmpty()) {
            this.valueFrequency.putAll(notExitValue.stream().collect(Collectors.groupingBy(x -> x, Collectors.counting())));
            rebuildTree();
        }
        List<T> exitValue = LambdaUtil.filterList(valueList, x -> valueFrequency.get(x) != null);
        if (!exitValue.isEmpty()) {
            exitValue.forEach(x -> valueFrequency.put(x, valueFrequency.get(x) + 1L));
            rebuildTree();
        }
        Map<String, String> newAndAncientValueMapping = rebuildCompressMappingResult();
        rebuildCompressResult(newAndAncientValueMapping);
        postHandleBuildTree();
    }

    @Override
    public void deleteValueList(List<T> valueList) {
        throwFunctionSupportException();
        Set<T> set = new HashSet<>(valueList);
        this.valueList.removeAll(set);
        set.forEach(valueFrequency::remove);
        List<HuffmanNode<T>> allHuffmanTreeList = getAllHuffmanTreeList();
        allHuffmanTreeList.removeAll(set.stream().map(x->getHuffmanLeaf(x)).collect(Collectors.toSet()));
        doRebuildTree(allHuffmanTreeList);
        rebuildCompressMappingResult();
    }

    @Override
    public void addHuffmanTree(HuffmanTree<T> huffmanTree) {
        buildValueFrequencyWhenAddTree(huffmanTree);
        buildTree(huffmanTree);
        buildCompressDataWhenAddTree(huffmanTree);
    }

    protected void buildCompressDataWhenAddTree(HuffmanTree<T> huffmanTree) {
        Map<String, String> originalAndFreshValueMapping = rebuildCompressMappingResult();
        RebuildCompressResultVO originalRebuildCompressResultVO = new RebuildCompressResultVO(originalAndFreshValueMapping,this.compressResult,new byte[calculateCompressResultLength()],lastBitIndex);
        changeOriginalCompressResult(originalRebuildCompressResultVO);
        byte[] newAddComressResult = buildCompressResult(huffmanTree);
        combineCompressResult(originalRebuildCompressResultVO, newAddComressResult);
        lastBitIndex = calculateLastBitIndex();
        this.compressResult = originalRebuildCompressResultVO.freshCompressResult;
        this.compressByteBuf = Unpooled.copiedBuffer(this.compressResult);
        postHandleBuildTree();
    }

    protected void buildValueFrequencyWhenAddTree(HuffmanTree<T> huffmanTree) {
        Map<T, Long> addValueFrequency = huffmanTree.getValueFrequency();
        addValueFrequency.forEach((x,y)->{
            this.valueFrequency.merge(x,y,(i,j)->i+j);
        });
    }

    private byte[] buildCompressResult(HuffmanTree<T> huffmanTree) {
        Map<String, String> originalAndFreshCompressCodeMapping = new HashMap<>();
        huffmanTree.getCompressMappingResult().forEach((x,y)->{
            originalAndFreshCompressCodeMapping.put(y,this.compressMappingResult.get(x));
        });
        int count=(int) huffmanTree.getValueFrequency().keySet().stream().mapToLong(x -> huffmanTree.getValueFrequency().get(x) * compressMappingResult.get(x).length()).sum();
        RebuildCompressResultVO rebuildCompressResultVO = new RebuildCompressResultVO(originalAndFreshCompressCodeMapping,huffmanTree.getCompressResult(),new byte[calculateCompressResultLength(count)],huffmanTree.getLastBitIndex());
        changeOriginalCompressResult(rebuildCompressResultVO);
        System.out.println(rebuildCompressResultVO.lastBitIndex);
        return rebuildCompressResultVO.freshCompressResult;
    }

    protected void buildTree(HuffmanTree<T> huffmanTree) {
        List<HuffmanNode<T>> allHuffmanTreeList = getAllHuffmanTreeList();
        allHuffmanTreeList.addAll(huffmanTree.getAllHuffmanTreeList());
        doRebuildTree(allHuffmanTreeList);
    }
}
